/*
 * Copyright 2000 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "WtCollection.h"
#include "WtUtil.h"

/* Get the underlying collection from a Tcl object */

WtCollection *WtToCln(Tcl_Obj *obj)
{
  return (WtCollection *)(obj->internalRep.twoPtrValue.ptr1);
}

/* Create a new collection object */

Tcl_Obj *WtNewClnObj(Tcl_ObjType *type, WtClnItemType *itemType)
{
  WtCollection *cln;
  Tcl_Obj *obj;

  cln = (WtCollection *)ckalloc(sizeof(WtCollection));
  cln->itemType = itemType;
  cln->list = Tcl_NewListObj(0, NULL);
  Tcl_IncrRefCount(cln->list);

  obj = Tcl_NewObj();
  obj->internalRep.twoPtrValue.ptr1 = cln;
  obj->internalRep.twoPtrValue.ptr2 = NULL;
  obj->typePtr = type ? type : &WtClnType;
  Tcl_InvalidateStringRep(obj);

  return obj;
}

/* Get the size of a collection */

int WtClnSize(Tcl_Obj *cln, int *size, Tcl_Interp *interp)
{
  WtCollection *clnRep = WtToCln(cln);
  return Tcl_ListObjLength(interp, clnRep->list, size) == TCL_OK;
}

/* Get all the items */

Tcl_Obj *WtGetClnItems(Tcl_Obj *cln)
{
  return WtToCln(cln)->list;
}

/* Get all item keys */

int WtGetClnKeys(Tcl_Obj *cln, Tcl_Obj **keys, Tcl_Interp *interp)
{
  int ok = 1, size;
  Tcl_Obj *item, *obj;
  int i;

  if (!WtClnSize(cln, &size, interp)) {
    return 0;
  }

  *keys = Tcl_NewListObj(0, NULL);

  for (i = 0; i < size; i++) {
    if (!WtGetClnItemAtIndex(cln, i, &item, interp) ||
        !WtGetClnItemKey(cln, item, &obj, interp) ||
        Tcl_ListObjAppendElement(interp, *keys, obj) !=
            TCL_OK) {
      ok = 0;
      break;
    }
  }

  if (!ok) {
    Tcl_DecrRefCount(*keys);
    *keys = NULL;
  }

  return ok;
}

/* Get all item values */

int WtGetClnValues(Tcl_Obj *cln, Tcl_Obj **values,
    Tcl_Interp *interp)
{
  int ok = 1, size;
  Tcl_Obj *item, *obj;
  int i;

  if (!WtClnSize(cln, &size, interp)) {
    return 0;
  }

  *values = Tcl_NewListObj(0, NULL);

  for (i = 0; i < size; i++) {
    if (!WtGetClnItemAtIndex(cln, i, &item, interp) ||
        !WtGetClnItemValue(cln, item, &obj, interp) ||
        Tcl_ListObjAppendElement(interp, *values, obj) !=
            TCL_OK) {
      ok = 0;
      break;
    }
  }

  if (!ok) {
    Tcl_DecrRefCount(*values);
    *values = NULL;
  }

  return ok;
}

/* Get a collection item */

int WtGetClnItemAtIndex(Tcl_Obj *cln, int idx, Tcl_Obj **item,
    Tcl_Interp *interp)
{
  WtCollection *clnRep = WtToCln(cln);
  return Tcl_ListObjIndex(interp, clnRep->list, idx, item) ==
    TCL_OK;
}

/* Set a collection item */

int WtSetClnItemAtIndex(Tcl_Obj *cln, int idx, Tcl_Obj *obj,
    Tcl_Interp *interp)
{
  int ok = 1;
  WtCollection *clnRep = WtToCln(cln);

  WtCopyOnWrite(&clnRep->list);
  Tcl_InvalidateStringRep(cln);
  Tcl_IncrRefCount(obj);

  if (Tcl_ListObjReplace(interp, clnRep->list, idx,
      1, 1, &obj) != TCL_OK) {
    ok = 0;
  }

  Tcl_DecrRefCount(obj);

  return ok;
}

/* Add an item to a collection */

int WtAppendClnItem(Tcl_Obj *cln, Tcl_Obj *obj, Tcl_Interp *interp)
{
  WtCollection *clnRep = WtToCln(cln);
  WtCopyOnWrite(&clnRep->list);
  Tcl_InvalidateStringRep(cln);
  return Tcl_ListObjAppendElement(interp, clnRep->list, obj) ==
    TCL_OK;
}

/* Add a list of items to a collection */

int WtAppendClnItemList(Tcl_Obj *cln, Tcl_Obj *list,
    Tcl_Interp *interp)
{
  WtCollection *clnRep = WtToCln(cln);
  WtCopyOnWrite(&clnRep->list);
  Tcl_InvalidateStringRep(cln);
  return Tcl_ListObjAppendList(interp, clnRep->list, list) ==
    TCL_OK;
}

/* Find an item using a key */

int WtFindClnItemIndex(Tcl_Obj *cln, Tcl_Obj *key, int start,
    int *index, Tcl_Obj **item, Tcl_Interp *interp)
{
  int ok = 1, i, size, comp = 0;

  if (!WtClnSize(cln, &size, interp)) {
    return 0;
  }

  for (i = start; i < size; i++) {
    if (!WtGetClnItemAtIndex(cln, i, item, interp) ||
        !WtCompareClnItem(cln, *item, key, &comp, interp)) {
      ok = 0;
      break;
    }
    if (!comp) {
      if (index) {
        *index = i;
      }
      return 1;
    }
  }

  if (index) {
    *index = -1;
  }
  *item = NULL;

  return ok;
}

/* Find an item in reverse order using a key */

int WtFindClnItemIndexReverse(Tcl_Obj *cln, Tcl_Obj *key, int start,
    int *index, Tcl_Obj **item, Tcl_Interp *interp)
{
  int ok = 1, size, i, comp;

  if (start == -1) {
    if (!WtClnSize(cln, &size, interp)) {
      return 0;
    }
    start = size;
  }

  for (i = start; i >= 0; i--) {
    if (!WtGetClnItemAtIndex(cln, i, item, interp) ||
        !WtCompareClnItem(cln, *item, key, &comp, interp)) {
      ok = 0;
      break;
    }
    if (!comp) {
      if (index) {
        *index = i;
      }
      return 1;
    }
  }

  if (index) {
    *index = -1;
  }
  *item = NULL;

  return ok;
}

/* Find the first matching item */

int WtFindClnItem(Tcl_Obj *cln, Tcl_Obj *key, Tcl_Obj **item,
    Tcl_Interp *interp)
{
  return WtFindClnItemIndex(cln, key, 0, NULL, item, interp);
}

/* Get all items matching the key */

int WtFindAllClnItems(Tcl_Obj *cln, Tcl_Obj *key,
    Tcl_Obj **items, Tcl_Interp *interp)
{
  int ok = 1;
  Tcl_Obj *item;
  int start = 0;

  *items = Tcl_NewListObj(0, NULL);

  for (;;) {
    if (!WtFindClnItemIndex(cln, key, start, &start, &item, interp)) {
      ok = 0;
      break;
    }
    if (!item) {
      break;
    }
    if (Tcl_ListObjAppendElement(interp, *items, item) != TCL_OK) {
      ok = 0;
      break;
    }
  }

  if (!ok) {
    Tcl_DecrRefCount(*items);
    *items = NULL;
  }

  return ok;
}

/* Get the key from all items that match the search key */

int WtFindAllClnKeys(Tcl_Obj *cln, Tcl_Obj *key, Tcl_Obj **keys,
    Tcl_Interp *interp)
{
  int ok = 1;
  Tcl_Obj *item, *itemKey;
  int start = 0;

  *keys = Tcl_NewListObj(0, NULL);

  for (;;) {
    if (!WtFindClnItemIndex(cln, key, start, &start, &item, interp)) {
      ok = 0;
      break;
    }
    if (!item) {
      break;
    }
    if (!WtGetClnItemKey(cln, item, &itemKey, interp)) {
      ok = 0;
      break;
    }
    if (Tcl_ListObjAppendElement(interp, *keys, itemKey) != TCL_OK) {
      ok = 0;
      break;
    }
  }

  if (!ok) {
    Tcl_DecrRefCount(*keys);
    *keys = NULL;
  }

  return ok;
}

/* Get the value from all items that match the search key */

int WtFindAllClnValues(Tcl_Obj *cln, Tcl_Obj *key,
    Tcl_Obj **values, Tcl_Interp *interp)
{
  int ok = 1;
  Tcl_Obj *item, *itemValue;
  int start = 0;

  *values = Tcl_NewListObj(0, NULL);

  for (;;) {
    if (!WtFindClnItemIndex(cln, key, start, &start, &item, interp)) {
      ok = 0;
      break;
    }
    if (!item) {
      break;
    }
    if (!WtGetClnItemValue(cln, item, &itemValue, interp)) {
      ok = 0;
      break;
    }
    if (Tcl_ListObjAppendElement(interp, *values, itemValue) !=
        TCL_OK) {
      ok = 0;
      break;
    }
  }

  if (!ok) {
    Tcl_DecrRefCount(*values);
    *values = NULL;
  }

  return ok;
}

/* Remove a collection item */

int WtRemoveFromCln(Tcl_Obj *cln, Tcl_Obj *target, int limit,
    Tcl_Interp *interp)
{
  int ok = 1, size, i, count = 0;
  Tcl_Obj *item;
  WtCollection *clnRep = WtToCln(cln);

  if (limit == 0) {
    return ok;
  }

  if (!WtClnSize(cln, &size, interp)) {
    return 0;
  }

  for (i = size; i >= 0; i--) {
    if (!WtGetClnItemAtIndex(cln, i, &item, interp)) {
      ok = 0;
      break;
    }
    if (item == target) {
      WtCopyOnWrite(&clnRep->list);
      Tcl_InvalidateStringRep(cln);
      if (Tcl_ListObjReplace(interp, clnRep->list, i, 1, 0, NULL) !=
          TCL_OK) {
        ok = 0;
        break;
      }
      if (++count == limit && limit != -1) {
        break;
      }
    }
  }

  return ok;
}

/* Remove a collection item at an index */

int RemoveFromClnByIndex(Tcl_Obj *cln, int idx, Tcl_Interp *interp)
{
  WtCollection *clnRep = WtToCln(cln);
  WtCopyOnWrite(&clnRep->list);
  Tcl_InvalidateStringRep(cln);
  return Tcl_ListObjReplace(interp, clnRep->list, idx, 1, 0, NULL) ==
      TCL_OK;
}

/* Remove a collection item by key */

int WtRemoveFromClnByKey(Tcl_Obj *cln, Tcl_Obj *key, int limit,
    Tcl_Interp *interp)
{
  int ok = 1, size, i, comp, count = 0;
  Tcl_Obj *item;
  WtCollection *clnRep = WtToCln(cln);

  if (limit == 0) {
    return ok;
  }

  if (!WtClnSize(cln, &size, interp)) {
    return 0;
  }

  for (i = size; i >= 0; i--) {
    if (!WtGetClnItemAtIndex(cln, i, &item, interp) ||
        !WtCompareClnItem(cln, item, key, &comp, interp)) {
      ok = 0;
      break;
    }
    if (!comp) {
      WtCopyOnWrite(&clnRep->list);
      Tcl_InvalidateStringRep(cln);
      if (Tcl_ListObjReplace(interp, clnRep->list, i, 1, 0, NULL) !=
          TCL_OK) {
        ok = 0;
        break;
      }
      if (++count == limit && limit != -1) {
        break;
      }
    }
  }

  return ok;
}

/* Remove a list of collection items by key */

int WtRemoveFromClnByKeyList(Tcl_Obj *cln, Tcl_Obj *list,
    int limit, Tcl_Interp *interp)
{
  int ok = 1, i, j, comp, count = 0, len;
  Tcl_Obj *item, *key;
  WtCollection *clnRep = WtToCln(cln);

  if (limit == 0) {
    return ok;
  }

  if (Tcl_ListObjLength(interp, list, &len) != TCL_OK ||
      !WtClnSize(cln, &i, interp)) {
    ok = 0;
  } else {
    for (; i >= 0; i--) {
      if (!WtGetClnItemAtIndex(cln, i, &item, interp)) {
        ok = 0;
        break;
      } else {
        for (j = 0; j < len; j++) {
          if (!Tcl_ListObjIndex(interp, list, i, &key)) {
            ok = 0;
            break;
          } else if (!WtCompareClnItem(cln, item, key, &comp, interp)) {
            ok = 0;
            break;
          }
          if (!comp) {
            WtCopyOnWrite(&clnRep->list);
            Tcl_InvalidateStringRep(cln);
            if (Tcl_ListObjReplace(interp, clnRep->list, i, 1, 0, NULL) !=
                TCL_OK) {
              ok = 0;
              break;
            }
            ++count;
            break;
          }
        }
        if (!ok) {
          break;
        }
        if (count == limit && limit != -1) {
          break;
        }
      }
    }
  }

  return ok;
}

/* Clear the contents of a collection */

void WtClearCollection(Tcl_Obj *cln)
{
  WtCollection *clnRep = WtToCln(cln);
  WtCopyOnWrite(&clnRep->list);
  Tcl_DecrRefCount(clnRep->list);
  clnRep->list = Tcl_NewListObj(0, NULL);
  Tcl_IncrRefCount(clnRep->list);
  Tcl_InvalidateStringRep(cln);
}

/* Update a Tcl array from a collection object */

int WtClnToArray(Tcl_Interp *interp, Tcl_Obj *cln,
    Tcl_Obj *arrName, int multiVals)
{
  int ok = 1, size, i;
  WtCollection *clnRep = WtToCln(cln);
  Tcl_Obj *item, *key, *val, *slot, *list;
  Tcl_ObjType *listType = Tcl_GetObjType("list");

  if (!WtClnSize(cln, &size, interp)) {
    return 0;
  }
  
  for (i = 0; i < size; ++i) {
    if (!WtGetClnItemAtIndex(cln, i, &item, interp)) {
      ok = 0;
      break;
    }
    if (!WtGetClnItemKey(cln, item, &key, interp) ||
        !WtGetClnItemValue(cln, item, &val, interp)) {
      ok = 0;
      break;
    }

    slot = Tcl_ObjGetVar2(interp, arrName, key, 0);

    if (multiVals) {
      if (!slot) {
        list = Tcl_NewListObj(1, &val);
        if (!Tcl_ObjSetVar2(interp, arrName, key, list,
            TCL_LEAVE_ERR_MSG)) {
          ok = 0;
          break;
        }
      } else {
        if (Tcl_IsShared(slot)) {
          list = Tcl_DuplicateObj(slot);
          Tcl_IncrRefCount(list);
        } else {
          list = slot;
        }
        if (Tcl_ListObjAppendElement(interp, list, val) != TCL_OK) {
          ok = 0;
        } else if (!Tcl_ObjSetVar2(interp, arrName, key, list,
            TCL_LEAVE_ERR_MSG)) {
          ok = 0;
        }
        if (list != slot) {
          Tcl_DecrRefCount(list);
        }
        if (!ok) {
          break;
        }
      }
    } else if (!Tcl_ObjSetVar2(interp, arrName, key, val,
        TCL_LEAVE_ERR_MSG)) {
      ok = 0;
      break;
    }
  }

  return ok;
}

int WtConvertToClnObj(Tcl_Obj *obj, Tcl_ObjType *type,
    Tcl_Interp *interp)
{
  return WtClnObjSetFromAny(interp, obj, type) == TCL_OK;
}

/* Collection object type */

struct Tcl_ObjType WtClnType =
{
  "collection",
  WtClnFree,
  WtClnDup,
  WtClnUpdateString,
  WtClnSetFromAny
};

/* Collection object procs */

void WtClnFree(Tcl_Obj *cln)
{
  WtClnObjFree(cln);
}

void WtClnDup(Tcl_Obj *src, Tcl_Obj *dest)
{
  WtClnObjDup(src, dest);
}

void WtClnUpdateString(Tcl_Obj *cln)
{
  WtClnObjUpdateString(cln);
}

int WtClnSetFromAny(Tcl_Interp *interp, Tcl_Obj *obj)
{
  return WtClnObjSetFromAny(interp, obj, &WtClnType);
}

void WtClnObjFree(Tcl_Obj *cln)
{
  WtCollection *clnRep = WtToCln(cln);
  Tcl_DecrRefCount(clnRep->list);
  ckfree((char *)clnRep);
}

void WtClnObjDup(Tcl_Obj *src, Tcl_Obj *dest)
{
  WtCollection *srcRep = WtToCln(src);
  WtCollection *destRep = (WtCollection *)ckalloc(sizeof(WtCollection));
  destRep->list = Tcl_DuplicateObj(srcRep->list);
  Tcl_IncrRefCount(destRep->list);
  destRep->itemType = srcRep->itemType;
  dest->typePtr = src->typePtr;
  dest->internalRep.twoPtrValue.ptr1 = destRep;
  dest->internalRep.twoPtrValue.ptr2 = NULL;
}

void WtClnObjUpdateString(Tcl_Obj *cln)
{
  WtUpdateProtectedObjString(cln);
}

int WtClnObjSetFromAny(Tcl_Interp *interp, Tcl_Obj *obj, Tcl_ObjType *type)
{
  return WtSetProtectedObjFromAny(interp, obj, type);
}

/* Compare an item to a search key */

int WtCompareClnItem(Tcl_Obj *cln, Tcl_Obj *item, Tcl_Obj *key,
    int *result, Tcl_Interp *interp)
{
  int ok = 1;
  WtCollection *clnRep = WtToCln(cln);
  char *itemStr, *keyStr;

  if (item == key) {
    *result = 0;
  } else if (clnRep->itemType && clnRep->itemType->compare) {
    if (!clnRep->itemType->compare(item, key, result, interp)) {
      ok = 0;
    }
  } else {
    itemStr = WtToString(item);
    keyStr = WtToString(key);
    *result = strcmp(itemStr, keyStr);
  }

  return ok;
}

/* Get the key from an item */

int WtGetClnItemKey(Tcl_Obj *cln, Tcl_Obj *item,
    Tcl_Obj **key, Tcl_Interp *interp)
{
  int ok = 1;
  WtCollection *clnRep = WtToCln(cln);
  if (clnRep->itemType && clnRep->itemType->getKey) {
    ok = clnRep->itemType->getKey(item, key, interp);
  } else {
    *key = item;
  }
  return ok;
}

/* Get the value from an item */

int WtGetClnItemValue(Tcl_Obj *cln, Tcl_Obj *item,
    Tcl_Obj **value, Tcl_Interp *interp)
{
  int ok = 1;
  WtCollection *clnRep = WtToCln(cln);
  if (clnRep->itemType && clnRep->itemType->getValue) {
    ok = clnRep->itemType->getValue(item, value, interp);
  } else {
    *value = item;
  }
  return ok;
}
